use std::cell::RefCell;
use std::rc::Rc;

use std::collections::HashMap;

pub enum ActionDirect {
    Start,
    Top,
    Right,
    Bottom,
    Left,
    NoFound,
}

#[derive(Debug)]
pub enum PointType {
    Start,
    End,
    Path,
    Wall,
    Edge,
}
#[derive(Debug)]
pub struct PathPoint {
    pub point: (usize, usize),
    pub cell_type: PointType,
    pub top_valid: Option<bool>,
    pub right_valid: Option<bool>,
    pub bottom_valid: Option<bool>,
    pub left_valid: Option<bool>,
}

impl PathPoint {
    pub fn new(point: (usize, usize), content: char) -> Self {
        PathPoint {
            point: point,
            cell_type: match content {
                'S' => PointType::Start,
                'E' => PointType::End,
                '.' => PointType::Path,
                '#' => PointType::Wall,
                _ => PointType::Edge,
            },
            top_valid: None,
            right_valid: None,
            bottom_valid: None,
            left_valid: None,
        }
    }
}

pub fn solve_maze(
    maze: Vec<Vec<char>>,
    start: (usize, usize),
    end: (usize, usize),
) -> Vec<(usize, usize)> {
    // Your code here
    let mut path: Vec<(usize, usize)> = Vec::new();
    let mut failed_path: Vec<(usize, usize)> = Vec::new();
    let mut record_path: Vec<(usize, usize, ActionDirect)> = Vec::new();
    // translate the maze to my struct vec

    let maze_deep = maze.len();
    let maze_width = maze[0].len();
    let max_deep_idx = maze_deep - 1;
    let max_width_idx = maze_width - 1;

    let mut path_hm: HashMap<(usize, usize), PathPoint> = HashMap::new();

    // for row in maze {
    //     for column in row {
    //     }
    // }

    for y in 0..maze_deep {
        for x in 0..maze_width {
            let mut c = PathPoint::new((x, y), maze[y][x]);
            if x == 0 || x == max_width_idx || y == 0 || y == max_deep_idx {
                if x == 0 {
                    c.left_valid = None;
                    c.right_valid = match maze[y][x + 1] {
                        'S' | 'E' | '.' => Some(true),
                        _ => Some(false),
                    };
                    if y == 0 {
                        c.top_valid = None;
                        c.bottom_valid = match maze[y + 1][x] {
                            'S' | 'E' | '.' => Some(true),
                            _ => Some(false),
                        };
                    } else {
                        c.bottom_valid = None;
                        c.top_valid = match maze[y - 1][x] {
                            'S' | 'E' | '.' => Some(true),
                            _ => Some(false),
                        };
                    }
                } else {
                    // x == max_width_idx
                    c.right_valid = None;
                    c.left_valid = match maze[y][x - 1] {
                        'S' | 'E' | '.' => Some(true),
                        _ => Some(false),
                    };
                    // -- y
                    if y == 0 {
                        c.top_valid = None;
                        c.bottom_valid = match maze[y + 1][x] {
                            'S' | 'E' | '.' => Some(true),
                            _ => Some(false),
                        };
                    } else {
                        c.bottom_valid = None;
                        c.top_valid = match maze[y - 1][x] {
                            'S' | 'E' | '.' => Some(true),
                            _ => Some(false),
                        };
                    }
                }
            } else {
                c.top_valid = match maze[y - 1][x] {
                    'S' | 'E' | '.' => Some(true),
                    _ => Some(false),
                };
                c.right_valid = match maze[y][x + 1] {
                    'S' | 'E' | '.' => Some(true),
                    _ => Some(false),
                };
                c.bottom_valid = match maze[y + 1][x] {
                    'S' | 'E' | '.' => Some(true),
                    _ => Some(false),
                };
                c.left_valid = match maze[y][x - 1] {
                    'S' | 'E' | '.' => Some(true),
                    _ => Some(false),
                };
            }

            path_hm.insert((x, y), c);
            //cell_vec.push(c);
        }
    }

    let hh = path_hm;

    path.push(start);
    record_path.push((start.0, start.1, ActionDirect::Start));
    // check the cell's 4 direction, top, right, bottom, left
    let mut sear_x = start.0;
    let mut sear_y = start.1;

    while true {
        let mut search = get_valid_point(sear_x, sear_y, &hh, &failed_path);

        let next_pp = hh.get(&(search.0, search.1)).unwrap();
        // keep the while continue, set new sear_x, sear_y
        sear_x = next_pp.point.0;
        sear_y = next_pp.point.1;
        // check new path
        if path.contains(&(sear_x, sear_y)) {
            // get the exists p, means the line go back. so pop the latest p, put it to the failed vec.
            failed_path.push(path.pop().unwrap());
        } else {
            record_path.push((sear_x, sear_y, search.2));
            path.push((sear_x, sear_y));
        }
    }

    path
}

// fn check_path_point(point: (usize, usize), max_x: usize, max_y: usize) -> Vec<(usize, usize)> {

// }

fn get_valid_point(
    sx: usize,
    sy: usize,
    all_pp: &HashMap<(usize, usize), PathPoint>,
    failed_pp: &Vec<(usize, usize)>,
) -> (usize, usize, ActionDirect) {
    let mut tx = sx;
    let mut ty = sy;
    let mut fd = ActionDirect::Top;

    let spp = all_pp.get(&(sx, sy)).unwrap();
    if spp.top_valid == Some(true) {
        ty -= 1;
        fd = ActionDirect::Bottom;
    } else if spp.right_valid == Some(true) {
        tx += 1;
        fd = ActionDirect::Left;
    } else if spp.bottom_valid == Some(true) {
        ty += 1;
        fd = ActionDirect::Top;
    } else if spp.left_valid == Some(true) {
        tx -= 1;
        fd = ActionDirect::Right;
    } else {
        // if we can get this point, means exists one point connect to this point.
        // so, we must not get the point which has on valid direct.
        unreachable!();
        fd = ActionDirect::NoFound;
    }

    // let tpp = all_pp.get(&(tx, ty)).unwrap();
    (tx, ty, fd)
}
